package org.example.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.example.handler.KeyPrefixHandler;
import org.example.utils.SpringUtils;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;

/**
 * 基于redisson的redis配置
 *
 * @author huhao
 */
@Slf4j
@EnableCaching
@AutoConfiguration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfiguration {

    /**
     * redis集群配置 yml
     * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
     * spring.data:
     * redis:
     * cluster:
     * nodes:
     * - 192.168.0.100:6379
     * - 192.168.0.101:6379
     * - 192.168.0.102:6379
     * # 密码
     * password:
     * # 连接超时时间
     * timeout: 10s
     * # 是否开启ssl
     * ssl.enabled: false
     * redisson:
     * # 线程池数量
     * threads: 16
     * # Netty线程池数量
     * nettyThreads: 32
     * # 集群配置
     * clusterServersConfig:
     * # 客户端名称
     * clientName: ${ruoyi.name}
     * # master最小空闲连接数
     * masterConnectionMinimumIdleSize: 32
     * # master连接池大小
     * masterConnectionPoolSize: 64
     * # slave最小空闲连接数
     * slaveConnectionMinimumIdleSize: 32
     * # slave连接池大小
     * slaveConnectionPoolSize: 64
     * # 连接空闲超时，单位：毫秒
     * idleConnectionTimeout: 10000
     * # 命令等待超时，单位：毫秒
     * timeout: 3000
     * # 发布和订阅连接池大小
     * subscriptionConnectionPoolSize: 50
     * # 读取模式
     * readMode: "SLAVE"
     * # 订阅模式
     * subscriptionMode: "MASTER"
     */
    private final RedissonProperties redissonProperties;

    /**
     * Redis配置构造函数
     *
     * @param redissonProperties Redisson属性配置对象
     */
    public RedisConfiguration(RedissonProperties redissonProperties) {
        // 将传入的Redisson属性配置对象赋值给类的成员变量redissonProperties
        this.redissonProperties = redissonProperties;
    }

    /**
     * 自定义Redisson配置
     *
     * @return 返回RedissonAutoConfigurationCustomizer类型的自定义配置器
     */
    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            // 设置Redisson配置参数
            config
                    // 设置线程池大小
                    .setThreads(redissonProperties.getThreads())
                    // 设置Netty线程池大小
                    .setNettyThreads(redissonProperties.getNettyThreads())
                    // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
                    .setUseScriptCache(true)
                    // 设置编解码器
                    .setCodec(this.timeJacksonCodecForRedisson());
            // 如果当前环境是虚拟环境
            if (SpringUtils.isVirtual()) {
                // 设置Netty执行器为VirtualThreadTaskExecutor
                config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
            }
            // 如果单机模式
            if ("single".equals(redissonProperties.getType())) {
                // 获取单机服务配置
                RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
                // 使用单机模式
                config.useSingleServer()
                        //设置redis key前缀
                        .setNameMapper(
                                new KeyPrefixHandler(redissonProperties.getKeyPrefix())
                        )
                        // 设置连接超时时间
                        .setTimeout(singleServerConfig.getTimeout())
                        // 设置客户端名称
                        .setClientName(singleServerConfig.getClientName())
                        // 设置空闲连接超时时间
                        .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
                        // 设置订阅连接池大小
                        .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
                        // 设置连接池中连接的最小空闲数
                        .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
                        // 设置连接池最大连接数
                        .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
            }
            // 如果集群模式
            else if ("cluster".equals(redissonProperties.getType())) {
                // 集群配置方式 参考下方注释
                // 获取集群服务配置
                RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
                // 使用集群模式
                config.useClusterServers()
                        //设置redis key前缀
                        .setNameMapper(
                                new KeyPrefixHandler(redissonProperties.getKeyPrefix())
                        )
                        // 设置连接超时时间
                        .setTimeout(clusterServersConfig.getTimeout())
                        // 设置客户端名称
                        .setClientName(clusterServersConfig.getClientName())
                        // 设置空闲连接超时时间
                        .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
                        // 设置订阅连接池大小
                        .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
                        // 设置主节点连接池中的最小空闲连接数
                        .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
                        // 设置主节点连接池最大连接数
                        .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
                        // 设置从节点连接池中的最小空闲连接数
                        .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
                        // 设置从节点连接池最大连接数
                        .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
                        // 设置读取模式
                        .setReadMode(clusterServersConfig.getReadMode())
                        // 设置订阅模式
                        .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
            }
            // 未设置就报错
            else {
                throw new RuntimeException("redis集群模式配置异常: " + redissonProperties.getType());
            }
            // 记录日志，表示redis配置初始化完成
            log.info("初始化 redis 配置");
        };
    }

    /**
     * @return CompositeCodec
     */
    private CompositeCodec timeJacksonCodecForRedisson() {
        // 创建JavaTimeModule实例
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        // 创建DateTimeFormatter实例，设置日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 为LocalDateTime类型添加自定义的序列化器
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
        // 为LocalDateTime类型添加自定义的反序列化器
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
        // 创建ObjectMapper实例
        ObjectMapper om = new ObjectMapper();
        // 注册JavaTimeModule到ObjectMapper中
        om.registerModule(javaTimeModule);
        // 设置ObjectMapper的时区为系统默认时区
        om.setTimeZone(TimeZone.getDefault());
        // 设置ObjectMapper的可见性，以便所有属性都可见
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型，类必须是非final修饰的。序列化时将对象全类名一起保存下来
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 创建TypedJsonJacksonCodec实例
        TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
        // 创建CompositeCodec实例，组合序列化key使用String，内容使用通用json格式
        // 组合序列化 key 使用 String 内容使用通用 json 格式
        return new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
    }
}
